package com.coffee.ws;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.coffee.ws.command.CoffeeCommandEnum;
import com.coffee.ws.command.CommandModel;
import com.coffee.ws.command.CommandModelMapping;
import com.coffee.ws.handler.*;
import com.coffee.ws.metadata.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.*;

/**
 * @author: songkui
 * @since: 2023/3/6 15:35
 */
@Slf4j
public class WebSocketInitialize {

    public static void initializeCommandModel(Map<Integer, CoffeeCommandEnum> commandEnumMap) throws Exception {
        Map<Integer, CommandModel> modelMap = new HashMap<>();

        Collection<CoffeeCommandEnum> collection = commandEnumMap.values();
        Iterator<CoffeeCommandEnum> codeIterator = collection.iterator();
        int textCmd = 0;
        int chatCmd = 0;
        int binaryCmd = 0;
        while (codeIterator.hasNext()) {
            CoffeeCommandEnum v = codeIterator.next();
            CommandModel coffeeCommandModel = new CommandModel();
            coffeeCommandModel.setCmd(v.cmd());
            coffeeCommandModel.setVerifyType(v.verifyType());
            coffeeCommandModel.setCmdName(v.cmdName());
            coffeeCommandModel.setMessageType(v.messageType());

            CoffeeMessageType messageType = v.messageType();
            switch (messageType) {
                case Text:
                    ++textCmd;
                    coffeeCommandModel.setTextRequest((CoffeeTextMessage) v.request().getConstructor().newInstance());
                    break;
                case Binary:
                    ++binaryCmd;
                    coffeeCommandModel.setBinaryRequest((BasicBinaryMessage) v.request().getConstructor().newInstance());
                    break;
                case Chat:
                    ++chatCmd;
                    coffeeCommandModel.setBinaryRequest(new CoffeeChatMessage());
                    break;

            }

            if (messageType.equals(CoffeeMessageType.Text) && null == coffeeCommandModel.getTextRequest()){
                throw new Exception("CommandModel "+ v.cmdName() +" CoffeeTextRequest is null");
            }else if (messageType.equals(CoffeeMessageType.Binary) && null == coffeeCommandModel.getBinaryRequest()){
                throw new Exception("CommandModel "+ v.cmdName() +" CoffeeBinaryRequest is null");
            }

            modelMap.put(v.cmd(), coffeeCommandModel);
        }

        CommandModelMapping.initializeCommandModel(modelMap);
        log.info("Coffee Command Model : textCmd size {};  chatCmd size {};  binaryCmd size {}", textCmd, chatCmd, binaryCmd);

    }

    public static void initializeHandlerMapping(String handlerPackagePath, Map<Integer, CoffeeCommandEnum> commandEnumMap) throws Exception {

        Map<Integer, CoffeeTextHandler> textHandlerMap = new HashMap<>();
        Map<Integer, CoffeeChatHandler> chatHandlerMap = new HashMap<>();
        Map<Integer, CoffeeBinaryHandler> binaryHandlerMap = new HashMap<>();


        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new AnnotationTypeFilter(CoffeeHandler.class));
        Set<BeanDefinition> components = provider.findCandidateComponents(handlerPackagePath);
        Set<Integer> cmdSet = new HashSet<>();
        for (BeanDefinition component : components) {
            Class clazz = Class.forName(component.getBeanClassName());
            if (clazz.isAnnotationPresent(CoffeeHandler.class)) {
                CoffeeHandler coffeeHandler = (CoffeeHandler) clazz.getAnnotation(CoffeeHandler.class);
                if (null == coffeeHandler || StrUtil.isEmpty(coffeeHandler.value())) {
                    throw new Exception("Websocket handler error : '" + clazz.getName() + "' CoffeeHandler error ");
                }

                CoffeeCommandEnum commandEnum = cmdNameToCmd(commandEnumMap, coffeeHandler.value());
                int cmd = commandEnum.cmd();
                CoffeeMessageType messageType = commandEnum.messageType();
                switch (messageType) {
                    case Text: putTextHandlerMapping(textHandlerMap, cmd, clazz); break;
                    case Chat: putChatHandlerMapping(chatHandlerMap, cmd, clazz); break;
                    case Binary: putBinaryHandlerMapping(binaryHandlerMap, cmd, clazz); break;
                }

                cmdSet.add(cmd);
            }
        }

        if (cmdSet.size() != components.size()) {
            throw new Exception("Coffee websocket handler repetition");
        }

        HandlerMapping.initializeTextHandler(textHandlerMap);
        HandlerMapping.initializeBinaryHandler(binaryHandlerMap);
        HandlerMapping.initializeChatHandler(chatHandlerMap);

        log.info("Initialize textHandlerMap size {} success", textHandlerMap.size());
        log.info("Initialize chatHandlerMap size {} success", chatHandlerMap.size());
        log.info("Initialize binaryHandlerMap size {} success", binaryHandlerMap.size());

    }

    private static CoffeeCommandEnum cmdNameToCmd(Map<Integer, CoffeeCommandEnum> commandEnumMap, String cmdName) throws Exception {
        if (MapUtil.isEmpty(commandEnumMap)) {
            throw new Exception("CommandEnum is null, please implement interface CoffeeCommandEnum");
        }
        Collection<CoffeeCommandEnum> collection = commandEnumMap.values();
        Iterator<CoffeeCommandEnum> codeIterator = collection.iterator();
        while (codeIterator.hasNext()) {
            CoffeeCommandEnum v = codeIterator.next();
            if (cmdName.contains(".")) {
                cmdName = StrUtil.subAfter(cmdName, '.', true);
            }
            if (v.cmdName().equals(cmdName)) {
                return v;
            }
        }
        throw new Exception("'" + cmdName + "' CoffeeCommandEnum in not this cmdName");
    }


    private static void putChatHandlerMapping(Map<Integer, CoffeeChatHandler> handlerMap, int cmd, Class<CoffeeChatHandler> clazz) throws Exception {
        if (null == clazz || cmd == 0) {
            throw new Exception("build Bytebuffer Handler exception");
        }
        handlerMap.put(cmd, Optional.ofNullable(SpringBeanContext.getBean(clazz)).orElseThrow(() -> new Exception("build Bytebuffer handler Mapping no exception")));
    }

    private static void putTextHandlerMapping(Map<Integer, CoffeeTextHandler> handlerMap, int cmd, Class<CoffeeTextHandler> clazz) throws Exception {
        if (null == clazz || cmd == 0) {
            throw new Exception("build text Handler exception");
        }
        handlerMap.put(cmd, Optional.ofNullable(SpringBeanContext.getBean(clazz)).orElseThrow(() -> new Exception("build text handler Mapping no exception")));
    }

    private static void putBinaryHandlerMapping(Map<Integer, CoffeeBinaryHandler> handlerMap, int cmd, Class<CoffeeBinaryHandler> clazz) throws Exception {
        if (null == clazz || cmd == 0) {
            throw new Exception("build text Handler exception");
        }
        handlerMap.put(cmd, Optional.ofNullable(SpringBeanContext.getBean(clazz)).orElseThrow(() -> new Exception("build text handler Mapping no exception")));
    }


}
